package calao.storage;

import java.io.PrintWriter;
import java.io.StringWriter;

import calao.util.Printable;

public class Record
implements Printable
{

	final Table table;
	final Object[] values;

	/** Gets the first value of a field.
	 */
	@SuppressWarnings("unchecked")
	public <T> T get(Field<T> field) 
	{
		checkField(field);
		return (T)directGet(field);
	}

	Object directGet(Field field) 
	{
		if (field.multiValued) {
			Object[] fvalues = (Object[])values[field.index];
			return fvalues.length>0? fvalues[0] : null; 
		}
		else return values[field.index];
	}

	/** Gets all values of a field.
	 */
	@SuppressWarnings("unchecked")
	public <T> T[] getAll(Field<T> field) 
	{
		checkField(field);
		return (T[])directGetAll(field);
	}

	Object[] directGetAll(Field field) 
	{
		if (field.multiValued) {
			Object array = values[field.index];
			if (array!=null) return (Object[])array;
		}
		else {
			Object value = values[field.index];
			if (value!=null) {
				Object[] fvalues = field.type.newValueArray(1);
				fvalues[0] = value;
				return fvalues;
			}
		}
		return field.type.newValueArray(0);
	}

	/** Sets a field value.
	 */
	public <T extends Object> void set(Field<T> field, T value) 
	{
		checkField(field);
		directSet(field, value);
	}

	void directSet(Field field, Object value) 
	{
		if (field.multiValued) {
			Object[] fvalues = field.type.newValueArray(1);
			fvalues[0] = value;
			this.values[field.index] = fvalues;
		}
		else values[field.index] = value;
	}

	/** Sets multiple values for a field.
	 */
	public <T> void set(Field<T> field, T[] values) 
	{
		checkField(field);
		directSet(field, values);
	}

	void directSet(Field field, Object[] values)
	{
		checkField(field);
		if (!field.multiValued) throw new UnsupportedOperationException();
		this.values[field.index] = values;
	}

	void checkField(Field field) 
	{
		if (field.table!=table) {
			throw new IllegalArgumentException("invalid field");
		}
	}

	/** Creates a new record.
	 * 
	 * @param table the associated table.
	 */
	public Record(Table table)
	{
		if (!table.isRegistered()) {
			throw new IllegalArgumentException("Table not registered");
		}
		this.table = table;
		values = new Object[table.getFieldCount()];
	}

	/** Gets the record table.
	 */
	public Table getTable()
	{
		return table;
	}

	/** Checks whether two records are identical.
	 * This method compares values of the primary index fields.
	 */
	@Override
	public boolean equals(Object obj)
	{
		try {
			Record rec = (Record)obj;
			for (Field field : table.primaryKeyIndex) {
				if (!directGet(field).equals(rec.directGet(field))) return false;
			}
		}
		catch (ClassCastException e) {
			return false;
		}
		return true;
	}

	/** Computes a hash code for this record.
	 * The result is computed based on values of the primary index fields.
	 */
	@Override
	public int hashCode()
	{
		int hashCode = 1;
		for (Field field : table.primaryKeyIndex) {
			Object val = directGet(field);
			hashCode = 31*hashCode + (val==null ? 0 : val.hashCode());
		}
		return hashCode;
	}

	/** Gets all integer values of a field.
	 */
	public int[] getAllInt(Field<Integer> field)
	{
		Integer[] array = (Integer[])getAll(field);
		int[] result = new int[array.length];
		for (int i=0; i<array.length; i++) result[i] = array[i];
		return result;
	}

	/** Sets all integer values of a field as an array of <i>int<i> values.
	 */
	public void set(Field<Integer> field, int[] values)
	{
		Integer[] array = new Integer[values.length];
		for (int i=0; i<array.length; i++) array[i] = values[i];
		set(field, array);
	}

	/** Gets the integer value of a field, or a default value.
	 */
	public int getInt(Field<Integer> field, int defaultValue)
	{
		Integer value = get(field);
		return value!=null? value : defaultValue;
	}

	/** Gets the integer value of a field, or 0 if the field has no value.
	 */
	public int getInt(Field<Integer> field)
	{
		return getInt(field, 0);
	}

	/** Gets the long value of a field, or a default value.
	 */
	public long getLong(Field<Long> field, long defaultValue)
	{
		Long value = get(field);
		return value!=null? value : defaultValue;
	}

	/** Gets the short value of a field, or 0 if the field has no value.
	 */
	public short getShort(Field<Short> field)
	{
		return getShort(field, (short)0);
	}

	/** Gets the short value of a field, or a default value.
	 */
	public short getShort(Field<Short> field, short defaultValue)
	{
		Short value = get(field);
		return value!=null? value : defaultValue;
	}

	/** Gets the long value of a field, or 0 if the field has no value.
	 */
	public long getLong(Field<Long> field)
	{
		return getLong(field, 0);
	}

	/** Gets the double value of a field, or a default value.
	 */
	public double getDouble(Field<Double> field, double defaultValue)
	{
		Double value = get(field);
		return value!=null? value : defaultValue;
	}

	/** Gets the double value of a field, or 0 if the field has no value.
	 */
	public double getDouble(Field<Double> field)
	{
		return getDouble(field, 0);
	}

	public void printTo(PrintWriter writer)
	{
		writer.print('{');
		int fcount = 0;
		for (Field field : table.getFieldList()) {
			FieldType.Enum ftype = field.type.enumValue;
			if (ftype!=FieldType.Enum.BINARY) {
				Object obj = values[field.index];
				if (obj==null) continue;
				if (fcount++!=0) writer.print(", ");
				writer.print(field.name);
				writer.print('=');
				if (field.multiValued) {
					Object[] fvalues = (Object[])obj;
					writer.print('[');
					for (int i=0; i<fvalues.length; i++) {
						if (i!=0) writer.print(',');
						printValue(writer, fvalues[i], ftype);
					}
					writer.print(']');
				}
				else {
					printValue(writer, obj, ftype);
				}
			}
		}		
		writer.print('}');
	}

	@Override
	public String toString()
	{
		StringWriter buffer = new StringWriter();
		printTo(new PrintWriter(buffer));
		return buffer.toString();
	}

	static void printValue(PrintWriter writer, Object value, FieldType.Enum type)
	{
		switch (type) {
		case OBJECT:
			new DataPrinter(writer).write(value);
			break;
		case INTEGER:
			writer.print(((Integer)value).intValue());
			break;
		case LONG:
			writer.print(((Long)value).longValue());
			break;
		case SHORT:
			writer.print(((Short)value).intValue());
			break;
		case DOUBLE:
			writer.print(((Double)value).doubleValue());
			break;
		case STRING: 
			DataPrinter.printStringValue(writer, (String)value);
			break;
		default:
			writer.print('?');
		}
	}

	
	public <T> boolean contains(Field<T> field, Object value)
	{
		for (Object rval : getAll(field)) if (value.equals(rval)) return true;
		return false;
	}
	
	public void clear(Field field)
	{
		values[field.index] = null;
	}
	
	public Object[] getPrimaryKey()
	{
		Index index = table.primaryKeyIndex;
		int length = index.getFieldCount();
		Object[] key = new Object[length];
		for (int i=0; i<length; i++) key[i] = values[index.getField(i).index];
		return key;		
	}
	
}
